#Rem
	Header: - Boids flocking movement
#End

Strict

Import mojo
Import agfx


#Rem
	summary: - TBoidsRule
#End
Class TBoidsRule
	Field value:Float
	Field enable:Bool=True
	Field tmp_boids:TBoids
	Method Call:TVector2(b:TPivot) Abstract
End


#Rem
	summary: - Rule#1 - cohesion
	Boids try to fly towards the centre of mass of neighbouring boids.
	The purpose of this rule is to for boids to make sure they don't collide into each other. I simply look at each boid, and if it's within a defined small distance (say 100 units) of another boid move it as far away again as it already is. This is done by subtracting from a vector c the displacement of each boid which is near by. We initialise c to zero as we want this rule to give us a vector which when added to the current position moves a boid away from those near it.
	
	[code]
	Local pcj:TVector2 =  New TVector2()	
		
		For Local i:Int=0 to Self.count-1
			If bj.name<>Self.boid[i].name Then
				pcj = VecAdd(pcj,Self.boid[i].position)
			End If
		Next
	
		pcj = VecMult(pcj,1.0/(Self.count))
		pcj = VecMult(VecSub(pcj,bj.position),Self.speed_scale)
		
		Return pcj
	[/code]
	
#End
Class TRule_cohesion Extends TBoidsRule

	Method New(eff:Float)
		Self.value = eff
	End Method
	
	Method Call:TVector2(bj:TPivot)
		Local pcj:TVector2 =  New TVector2()	
		
		For Local i:Int=0 to Self.tmp_boids.count-1
			If bj.name<>Self.tmp_boids.boid[i].name Then
				pcj = VecAdd(pcj,Self.tmp_boids.boid[i].position)
			End If
		Next
	
		pcj = VecMult(pcj,1.0/(Self.tmp_boids.boid.Length()))
		pcj = VecMult(VecSub(pcj,bj.position),Self.tmp_boids.speed_scale)
		
		Return VecMult(pcj,Self.value)
	End Method	
End

#Rem
	summary: - Rule#2 - aligment
	Boids try to match velocity with near boids. 
	The purpose of this rule is to for boids to make sure they don't collide into each other. I simply look at each boid, and if it's within a defined small distance (say 100 units) of another boid move it as far away again as it already is. This is done by subtracting from a vector c the displacement of each boid which is near by. We initialise c to zero as we want this rule to give us a vector which when added to the current position moves a boid away from those near it.
	[code]
	Local c:TVector2 = New TVector2(0,0)
		
		For Local i:Int=0 to Self.tmp_boids.count-1
		
			If b.name<>Self.tmp_boids.boid[i].name Then
			
				If VecDist(b.position,Self.tmp_boids.boid[i].position)<b.radius Then
					c = VecSub(c,VecSub(self.tmp_boids.boid[i].position,b.position))
				End If
			
			End If
		Next
	[/code]
#End
Class TRule_aligment Extends TBoidsRule
	
	Method New(eff:Float)
		Self.value = eff
	End Method
	
	Method Call:TVector2(b:TPivot)
		Local c:TVector2 = New TVector2(0,0)
		
		For Local i:Int=0 to Self.tmp_boids.count-1
		
			If b.name<>Self.tmp_boids.boid[i].name Then
			
				If VecDist(b.position,Self.tmp_boids.boid[i].position)<b.radius Then
					c = VecSub(c,VecSub(self.tmp_boids.boid[i].position,b.position))
				End If
			
			End If
		Next
		Return VecMult(c,Self.value)
	End Method
End

#Rem
	summary: - Rule#3 - velocity
	This is similar to Rule 1, however instead of averaging the positions of the other boids we average the velocities. We calculate a 'perceived velocity', pvJ, then add a small portion (about an eighth) to the boid's current velocity.
	
	[code]
	Local pvj:TVector2 = New TVector2()
		
			For Local i:Int=0 to Self.tmp_boids.count-1
			If b.name<>Self.tmp_boids.boid[i].name Then
				pvj = VecAdd(pvj,Self.tmp_boids.boid[i].velocity)
			End If
		Next
	
		pvj = VecMult(pvj,1.0/Self.tmp_boids.boid.Length())
		
		pvj = VecSub(pvj,b.velocity)
		pvj = VecMult(pvj,1.0/8.0)
				
		Return VecMult(pvj,Self.value)
	[/code]
#End

Class TRule_velocity Extends TBoidsRule
	
	Method New(eff:Float)
		Self.value = eff
	End Method
	
	Method Call:TVector2(b:TPivot)
	Local pvj:TVector2 = New TVector2()
		
			For Local i:Int=0 to Self.tmp_boids.count-1
			If b.name<>Self.tmp_boids.boid[i].name Then
				pvj = VecAdd(pvj,Self.tmp_boids.boid[i].velocity)
			End If
		Next
	
		pvj = VecMult(pvj,1.0/Self.tmp_boids.boid.Length())
		
		pvj = VecSub(pvj,b.velocity)
		pvj = VecMult(pvj,1.0/8.0)
				
		Return VecMult(pvj,Self.value)
		
	End Method
End

#Rem
	summary: - Rule#4
	range bounce
	[code]
		Local bv:TVector2 = New TVector2()
		
			
			if b.position.x<Self.tmp_boids.min_range.x Then 
				bv = VecAdd(bv, New TVector2(100,0))
			ElseIf b.position.x>Self.tmp_boids.max_range.x Then
				bv = VecAdd(bv, New TVector2(-100,0))
			End
			
			if b.position.y<Self.tmp_boids.min_range.y Then 
				bv = VecAdd(bv, New TVector2(0,100))
			ElseIf b.position.y>Self.tmp_boids.max_range.y Then
				bv = VecAdd(bv, New TVector2(0,-100))
			End
		

		Return VecMult(bv,Self.value)	
	[/code]
#End
Class TRule_RangeBounce Extends TBoidsRule
	
	Method New(eff:Float)
		Self.value = eff
	End Method
	
	Method Call:TVector2(b:TPivot)
	Local bv:TVector2 = New TVector2()
		
			
			if b.position.x<Self.tmp_boids.min_range.x Then 
				bv = VecAdd(bv, New TVector2(100,0))
			ElseIf b.position.x>Self.tmp_boids.max_range.x Then
				bv = VecAdd(bv, New TVector2(-100,0))
			End
			
			if b.position.y<Self.tmp_boids.min_range.y Then 
				bv = VecAdd(bv, New TVector2(0,100))
			ElseIf b.position.y>Self.tmp_boids.max_range.y Then
				bv = VecAdd(bv, New TVector2(0,-100))
			End
		

		Return VecMult(bv,Self.value)
	End Method
End


#Rem
	summary: - Rule#5
	follow target position
	
	[code]
	Local tv:TVector2 = New TVector2()
		
			tv = VecMult( VecSub(self.tmp_boids.target_position,b.position),Self.tmp_boids.speed_scale)
				
		Return VecMult(tv,Self.value)
	[/code]
#End
Class TRule_FollowTarget Extends TBoidsRule
	
	Method New(eff:Float)
		Self.value = eff
	End Method
	
	Method Call:TVector2(b:TPivot)
		Local tv:TVector2 = New TVector2()
		
			tv = VecMult( VecSub(self.tmp_boids.target_position,b.position),Self.tmp_boids.speed_scale)
				
		Return VecMult(tv,Self.value)
	End Method
End

#Rem
	summary: - TBoids
#End
Class TBoids
	
	Private

	Public

	#Rem
		summary: list of boids as array
	#End
	Field rules:List<TBoidsRule>
	#Rem
		summary: list of boids as array
	#End
	Field boid:TPivot[]
	
	#Rem
		summary: count boids
	#End
	Field count:Int
	
	#Rem
		summary: offset on start from startup position
	#End
	Field offset:TVector2
	#Rem
		summary: startup position
	#End
	Field position:TVector2
		
	#Rem
		summary: max range 
	#End
	Field max_range:TVector2
	
	#Rem
		summary: nin range 
	#End
	Field min_range:TVector2
	
	#Rem
		summary: target position
	#End
	Field target_position:TVector2
	
	#Rem
		summary: speed scale
	#End
	Field speed_scale:Float = 0.01
	
	#Rem
		summary: limit speed
	#End
	Field speed_limit:Float = 5.0
	
	#Rem
		summary: constructor
		count:Int			- count
		offset:TVector2		- offset from start position
		position:TVector2 	- boid group start position
	#End
	Method New(count:Int,offset:TVector2,position:TVector2)	
		Self.offset = offset
		Self.position = position
		Self.count = count
		Self.boid = New TPivot[Self.count]
		Self.GenerateBoids()
		Self.max_range = New TVector2(DISPLAY.VSCREEN_WIDTH,DISPLAY.VSCREEN_HEIGHT)
		Self.min_range = New TVector2(offset.x,offset.y)
		Self.target_position = New TVector2(DISPLAY.VSCREEN_HALF_WIDTH,DISPLAY.VSCREEN_HALF_HEIGHT)
		Self.rules = New List<TBoidsRule>()
	End Method

	#Rem
		summary: generate boids
	#End
	Method GenerateBoids:Void()
		For Local i:Int=0 to Self.count-1
			Self.boid[i] = New TPivot("Boid_"+i,0,0,16,16)
			Self.boid[i].radius = 16
			Local ox:Int = self.offset.x -Rnd(0,self.offset.x/2.0)
			Local oy:Int = self.offset.y -Rnd(0,self.offset.y/2.0)
			Self.boid[i].position.Set(Self.position.x+ox,Self.position.y+oy)
			'Self.boid[i].position.Set(Self.position.x,Self.position.y)
			
		Next
	End Method
	
	
	#Rem
		summary: - add rule to list
	#End
	Method AddRule:Void(rul:TBoidsRule)
		rul.tmp_boids = Self
		self.rules.AddLast(rul)
	End Method
	
	
	#Rem
		summary: - Rule#6
		speed limit
	#End
	Method Rule_6_SpeedLimit:Void(b:TPivot)
			If b.speed>Self.speed_limit Then
				b.speed = Self.speed_limit
			End If
	End Method
	
	#Rem
		summary: Update boids
		call this in app OnUpdate() callback
	#End
	Method Update:Void()
		
		Local r:TBoidsRule

		For Local i:Int=0 to Self.count-1
			
			Local v:TVector2 = New TVector2()
			
			For r = eachin Self.rules
				v = VecAdd(v,r.Call(Self.boid[i]))
			Next
			
						
			Self.boid[i].velocity = VecAdd(Self.boid[i].velocity,v)
			
			boid[i].ApplyVelocity()
			
			Rule_6_SpeedLimit(boid[i])
			
			boid[i].MoveForward()
			
			boid[i].Update()
		Next
	End Method
	
	#Rem
		summary: - Render boids
		call this in app OnRender() callback
	#End
	Method Render:Void()
		For Local i:Int=0 to Self.count-1
			Self.boid[i].Draw()			
		Next
	End Method
End Class